'use strict';

const Service = require('egg').Service;

module.exports = class UserService extends Service {

  constructor(ctx) {
    super(ctx);

    // //// Attributes /////////////////////////////////////////////
    this.id = 0;
    this.username;
    this.password;
    this.salt;
    this.groupID = 2; // Default to Guest Group
    this.groupName = '<font color=red>' + this.ctx.__('unkown') + '</font>';
    this.rights = { view: 0, post: 0, edit: 0, delete: 0, upload: 0 };
    this.gender;
    this.email;
    this.homepage;
    this.hideEmail;
    this.lastVisit;
    this.lastIP;
    this.IP;
    this.articleCount;
    this.commentCount;
    this.connectionOpen;
    this.loggedIn = false;

    // //// Initialization //////////////////////////////////////////

    // Get user current IP
    this.IP = this.ctx.ip;
  }

  // //// Methods /////////////////////////////////////////////////

  // Register -----------------------
  async register(bCheckSCode) {
    let strError = '';

    if (this.ctx.app.theCache.settings.enableRegister != 1) {
      return '<li>' + this.ctx.__('reg_disabled') + '</li>';
    }

    // If user is already register
    if (this.ctx.session.registered || this.loggedIn) {
      return '<li>' + this.ctx.__('reg_already') + '</li>';
    }

    // Check Data
    strError += await this.fillFromPost(bCheckSCode);

    if (strError != '') {
      // Return with error message
      return strError;
    }
    // Clean up to avoid abuse
    this.ctx.session.lbsSecurityCode = null;

    // Insert DB Record
    await this.insert();

    // Make sure no double register is possible
    if (this.ctx.session.registered == undefined) {
      this.ctx.session.registered = true;
    }

    // Login now
    await this.login(this.ctx.body.username, this.ctx.body.password, 1, false, '', true);

    return false;

  }

  // Common Check for Post Data
  async fillFromPost(bCheckSCode, bEdit, bAdmin) {
    let strError = '';

    if ((!bEdit && (!this.ctx.body.username || !this.ctx.body.password || !this.ctx.body.repassword)) || !this.ctx.session.lbsSecurityCode) {
      strError += '<li>' + this.ctx.__('form_incomplete') + '</li>';
    }
    if (this.ctx.app.theCache.settings.enableSecurityCode == 1
      && bCheckSCode
      && this.ctx.session.lbsSecurityCode != this.ctx.body.scode) {
      strError += '<li>' + this.ctx.__('scode_invalid') + '</li>';
    }

    if (!bEdit
      && (!this.ctx.helper.checkUsername(this.ctx.body.username) ||
        this.ctx.body.username != this.ctx.helper.wordFilter(this.ctx.body.username))) {
      strError += '<li>' + this.ctx.__('username_invalid') + '</li>';
    }

    if (!bEdit) {
      if (await this.ctx.app.connBlog.query("SELECT user_id FROM blog_user WHERE user_name='" + this.ctx.helper.checkStr(this.ctx.body.username) + "'")) {
        strError += '<li>' + this.ctx.__('user_exist') + '</li>';
      } else {
        this.username = this.ctx.body.username;
      }
    }

    if (!bEdit) {
      if (this.ctx.body.password != this.ctx.body.repassword || !this.ctx.helper.checkPassword(this.ctx.body.password)) {
        strError += '<li>' + this.ctx.__('password_invalid') + '</li>';
      } else {
        this.salt = this.ctx.helper.randomStr(6);
        this.password = this.ctx.helper.SHA1(this.ctx.body.password + this.salt);
      }
    } else {
      if (this.ctx.body.password) {
        if (this.ctx.body.password != this.ctx.body.repassword || !this.ctx.helper.checkPassword(this.ctx.body.password)) {
          strError += '<li>' + this.ctx.__('password_invalid') + '</li>';
        } else {
          this.salt = this.ctx.helper.randomStr(6);
          this.password = this.ctx.helper.SHA1(this.ctx.body.password + this.salt);
        }
      } else if (!bAdmin) {
        this.salt = this.ctx.helper.randomStr(6);
        this.password = this.ctx.helper.SHA1(this.ctx.body.oldpassword + this.salt);
      }
    }

    if (!this.ctx.body.email) {
      this.email = '';
    } else if (!this.ctx.helper.checkEmail(this.ctx.body.email) && this.ctx.body.email) {
      strError += '<li>' + this.ctx.__('email_invalid') + '</li>';
    } else {
      this.email = this.ctx.body.email;
    }
    this.hideEmail = !!this.ctx.body.hideemail;

    this.gender = this.ctx.helper.checkInt(this.ctx.body.gender);
    if (this.gender < 0 || this.gender > 2) {
      this.gender = 0;
    }

    if (this.ctx.body.homepage) {
      this.homepage = this.ctx.helper.checkURL(this.ctx.body.homepage);
    } else {
      this.homepage = '';
    }

    return strError;
  }

  // Login --------------------------
  async login(strUsername, strPassword, intCookiesDay, bCheckSCode, strSCode, bNoSCode) {
    const { theCache, connBlog, session, func, cookies } = this.ctx;

    let strError = '';

    // Check if user is banned for login failure
    if ((new Date()) - session.loginban > 3 * 60 * 1000) {
      session.loginfail = null;
      session.loginban = null;
    }
    if (session.loginfail > 3) {
      session.loginban = new Date();
      session.lbsSecurityCode = null; // Clean up to avoid abuse
      return '<li>' + this.ctx.__('login_fail_ban') + '</li>';
    }

    // Check data
    if (!strUsername || !strPassword || (!bNoSCode && !session.lbsSecurityCode)) {
      strError += '<li>' + this.ctx.__('form_incomplete') + '</li>';
    }
    if (!func.checkUsername(strUsername)) {
      strError += '<li>' + this.ctx.__('username_invalid') + '</li>';
    }
    if (!func.checkPassword(strPassword)) {
      strError += '<li>' + this.ctx.__('password_invalid') + '</li>';
    }
    if (theCache.settings.enableSecurityCode == 1 && bCheckSCode && session.lbsSecurityCode != strSCode) {
      strError += '<li>' + this.ctx.__('scode_invalid') + '</li>';
    }

    if (strError != '') {
      // Return with error message
      return strError;
    }

    intCookiesDay = func.checkInt(intCookiesDay);
    if (!intCookiesDay) {
      intCookiesDay = 1;
    }
    const tDate = new Date();
    tDate.setTime(tDate.getTime() + intCookiesDay * 864E5);

    const tHashKey = func.SHA1(func.randomStr(6) + func.getDateTimeString());
    const arrUpdate = {
      user_IP: this.IP,
      user_hashKey: tHashKey,
      user_LastVisit: new Date()
    };
    // Check Password
    let tmpA = await connBlog.query("SELECT user_name,user_salt,user_password,user_id,user_groupID FROM blog_user WHERE user_name='" + strUsername + "' AND user_salt<>''", 1, 1);
    if (tmpA && tmpA.length) {
      tmpA = tmpA[0];
      if (tmpA.user_password == func.SHA1(`${strPassword}${tmpA.user_salt}`)) {
        // Update IP, lastVisitDate & hash key
        await connBlog.update('blog_user', arrUpdate, { user_id: tmpA.user_id });
        cookies.set('userid', tmpA.user_id, {
          expires: tDate,
        });
        cookies.set('hashkey', tHashKey, {
          expires: tDate,
        });

        session.lbsSecurityCode = null; // Clean up to avoid abuse
        await this.fill(tmpA);
        this.loggedIn = true;
        return false;
      }
      await this.logout(false);
      if (!session.loginfail) session.loginfail = 0;
      session.loginfail++;
      return '<li>' + this.ctx.__('login_fail') + '</li>';

      // Code below is only for transportion from old version
    } else {
      tmpA = await connBlog.query(
        "SELECT user_name,user_id,user_salt,user_groupID FROM blog_user WHERE user_name='" + strUsername +
        "' AND user_password='" + func.MD5(strPassword).toUpperCase() + "'", 1, 1)
      if (tmpA && tmpA.length) {
        tmpA = tmpA[0];
        if (!tmpA.user_salt) {
          // Update Password Encrytion
          const strSalt = func.randomStr(6);
          arrUpdate.user_Salt = strSalt; // Update Password
          arrUpdate.user_Password = func.SHA1(`${strPassword}${strSalt}`); // Update Password
          await connBlog.update('blog_user', arrUpdate, 'user_id=' + tmpA.user_id);
          cookies.set('userid', tmpA.user_id, {
            expires: tDate.getVarDate(),
          });
          cookies.set('hashkey', tHashKey, {
            expires: tDate.getVarDate(),
          });

          session.lbsSecurityCode = null; // Clean up to avoid abuse
          await this.fill(tmpA);
          this.loggedIn = true;
          return false;
        }
        await this.logout(false);
        if (!session.loginfail) session.loginfail = 0;
        session.loginfail++;
        return '<li>' + this.ctx.__('login_fail') + '</li>';

      }
    }

    await this.logout(false);
    return '<li>' + this.ctx.__('user_not_found') + '</li>';

  }

  // Logout --------------------------
  async logout(bCleanHashKey) {
    this.ctx.cookies.userid = null;
    this.ctx.cookies.hashkey = null;
    if (bCleanHashKey) {
      await this.ctx.app.connBlog.update('blog_user', { user_hashKey: '' }, { user_id: this.id });
    }
    this.loggedIn = false;
  }

  // Check User Cookies --------------------------
  async checkCookies(bCheckIP) {
    if (!String(this.ctx.cookies.get('userid')) || !String(this.ctx.cookies.get('hashkey'))) {
      await this.logout(false);
    } else {
      const intUserID = this.ctx.helper.checkInt(this.ctx.cookies.get('userid'));
      const strHashKey = String(this.ctx.cookies.get('hashkey'));
      const tmpA = await this.ctx.app.connBlog.query('SELECT user_id,user_ip,user_name,user_groupID FROM blog_user WHERE user_id=' + intUserID + " AND user_hashKey='" + this.ctx.helper.checkStr(strHashKey) + "' AND user_hashKey<>''", 1, 1);
      if (tmpA && tmpA.length) {
        if (bCheckIP && tmpA[0].user_ip != this.IP) {
          this.id = tmpA[0].user_id;
          await this.logout(true);
        } else {
          this.loggedIn = true;
          this.fill(tmpA[0]);
        }
      } else {
        await this.logout(false);
      }
    }
  }

  // Load User by ID --------------------------
  async loadByID(intID) {
    if (!this.ctx.helper.checkInt(intID)) {
      return this.ctx.__('invalid_parameter');
    }
    // Load from DB
    if (await this.load('*', 'user_id=' + this.ctx.helper.checkInt(intID))) {
      return false;
    }
    return this.ctx.__('user_not_found');

  }

  // Load User From DB --------------------------
  async load(strSelect, strWhere) {
    const tmpA = await this.ctx.app.connBlog.query('SELECT ' + strSelect + ' FROM blog_user WHERE ' + strWhere, 1, 1);
    if (tmpA && tmpA.length) {
      this.fill(tmpA[0]);
      return true;
    }
    return false;

  }

  // Fill User Object -------------------
  fill(arr) {
    this.id = arr.user_id;
    this.username = arr.user_name;
    this.password = arr.user_password;
    this.salt = arr.user_salt;
    this.groupID = arr.user_groupID;
    this.gender = arr.user_gender;
    this.email = arr.user_email;
    this.hideEmail = arr.user_hideEmail;
    this.homepage = arr.user_homepage;
    this.lastVisit = new Date(Number(arr.user_lastVisit));
    this.lastIP = arr.user_ip;
    this.articleCount = arr.user_articleCount;
    this.commentCount = arr.user_commentCount;
    this.getUserRights();
  }

  // Get User Rights
  getUserRights() {
    for (let i = 0; i < this.ctx.app.theCache.userGroup.length; i++) {
      if (this.groupID === this.ctx.app.theCache.userGroup[i].id) {
        this.groupName = this.ctx.app.theCache.userGroup[i].name;
        this.rights = this.ctx.app.theCache.userGroup[i].rights;
        return;
      }
    }
  }

  // Insert New User
  async insert() {
    const arrInsert = {
      user_Name: this.username,
      user_Password: this.password,
      user_Salt: this.salt,
      user_GroupID: 3, // Registered Group
      user_gender: this.gender,
      user_Email: this.email,
      user_homepage: this.homepage,
      user_HideEmail: this.hideEmail,
    };
    await this.ctx.app.connBlog.insert('blog_user', arrInsert);
    await this.ctx.app.connBlog.updateSQL('blog_settings', 'set_value0=set_value0+1', "set_name='counterUser'");
    await this.ctx.app.cache.updateGlobalCounter('counterUser', 1);
  }

  // Update User Profile
  async update() {
    const arrUpdate = {
      user_Name: this.username,
      user_Password: this.password,
      user_Salt: this.salt,
      user_GroupID: this.groupID,
      user_gender: this.gender,
      user_Email: this.email,
      user_homepage: this.homepage,
      user_HideEmail: this.hideEmail,
    };
    await this.ctx.app.connBlog.update('blog_user', arrUpdate, 'user_id=' + this.id);
  }

  // Delete User
  async doDelete() {
    await this.ctx.app.connBlog.doDelete('blog_user', 'user_id=' + this.id);
    await this.ctx.app.connBlog.updateSQL('blog_article', 'log_authorID=0', 'log_authorID=' + this.id);
    await this.ctx.app.connBlog.updateSQL('blog_comment', 'comm_authorID=0', 'comm_authorID=' + this.id);

    await this.ctx.app.connBlog.updateSQL('blog_guestbook', 'gb_userID=0', 'gb_userID=' + this.id);

    await this.ctx.app.connBlog.updateSQL('blog_settings', 'set_value0=set_value0-1', "set_name='counterUser'");
    await this.ctx.app.cache.updateGlobalCounter('counterUser', -1);
  }

  // Check View Permission for Articles -------------------------------------------------------
  checkViewPermission(objArticle) {
    return !(
      (
        objArticle.mode === 1 && this.rights.view < 1 ||
        objArticle.mode === 2 && (!this.loggedIn || this.rights.view < 1) ||
        (objArticle.mode === 3 || (objArticle.category && objArticle.category.hidden)) && (!this.loggedIn || this.rights.view < 2) ||
        objArticle.mode === 4 && (!this.loggedIn || this.rights.view < 3)
      ) && objArticle.authorID !== this.id
    );
  }

  // Record Visitor -------------------------------------------------------
  async recordVisitor() {
    let strTarget = String(this.ctx.url).replace('\\', '/');
    strTarget = strTarget.substr(strTarget.lastIndexOf('/'));
    strTarget += '?' + this.ctx.querystring;
    strTarget = strTarget.substr(0, 50);
    const browserCap = getBrowserCap(this.ctx.headers['User-Agent']); // strUA is defined in _common.asp
    const arrInfo = {
      vr_ip: this.IP,
      vr_browser: browserCap.name,
      vr_os: browserCap.os,
      vr_referer: this.ctx.request.originalUrl ? this.ctx.request.originalUrl.substr(0, 250) : '', // strReferer is defined in _common.asp
      vr_target: strTarget,
      vr_time: new Date()
    };

    let tmpA = await this.ctx.app.connBlog.query('SELECT count(vr_id) as cnt FROM blog_VisitorRecord');
    if (tmpA[0].cnt >= await this.ctx.app.cache.settings.maxVisitorRecord) {
      tmpA = await this.ctx.app.connBlog.query('SELECT vr_id FROM blog_visitorrecord ORDER BY vr_time ASC', 1, 1);
      await this.ctx.app.connBlog.update('blog_visitorrecord', arrInfo, 'vr_id=' + tmpA[0].vr_id);
    } else {
      await this.ctx.app.connBlog.insert('blog_visitorrecord', arrInfo);
    }

    await this.ctx.app.connBlog.updateSQL('blog_settings', 'set_value0=set_value0+1', "set_name='counterVisitor'");
    await this.ctx.app.cache.updateGlobalCounter('counterVisitor', 1);

  }

};


// Browser & OS Info Function -----------------------------------------
// This is a supressed version
function getBrowserCap(strUA) {
  const arrInfo = {
    name: 'Unkown',
    os: 'Unkown'
  };
  let idx,
    idxCol;
  let strType = '';

  // Clean up string
  strUA = strUA.toLowerCase();
  strUA = strUA.replace(/^\s+/, '');
  strUA = strUA.replace(/\s+$/, '');
  strUA = strUA.replace(/\n/, '');

  if (strUA.length < 10) return arrInfo;

  // Browser Check
  if (strUA.indexOf('mozilla') > -1) arrInfo.name = 'Mozilla';
  if (strUA.indexOf('icab') > -1) arrInfo.name = 'iCab';
  if (strUA.indexOf('lynx') > -1) arrInfo.name = 'Lynx';
  if (strUA.indexOf('links') > -1) arrInfo.name = 'Links';
  if (strUA.indexOf('elinks') > -1) arrInfo.name = 'ELinks';
  if (strUA.indexOf('jbrowser') > -1) arrInfo.name = 'JBrowser';
  if (strUA.indexOf('gecko') > -1) {
    strType = '[Gecko]';
    arrInfo.name = 'Mozilla';
    if (strUA.indexOf('aol') > -1) arrInfo.name = 'AOL';
    if (strUA.indexOf('netscape') > -1) arrInfo.name = 'Netscape';
    if (strUA.indexOf('firefox') > -1) arrInfo.name = 'FireFox';
    if (strUA.indexOf('chimera') > -1) arrInfo.name = 'Chimera';
    if (strUA.indexOf('camino') > -1) arrInfo.name = 'Camino';
    if (strUA.indexOf('galeon') > -1) arrInfo.name = 'Galeon';
    if (strUA.indexOf('k-meleon') > -1) arrInfo.name = 'K-Meleon';
    arrInfo.name += strType;

  }
  if (strUA.indexOf('konqueror') > -1) {
    arrInfo.name = 'Konqueror';
  }
  if (strUA.indexOf('bot') > -1 || strUA.indexOf('crawl') > -1) {
    strType = '[Bot/Crawler]';
    arrInfo.name = '';
    if (strUA.indexOf('grub') > -1) arrInfo.name = 'Grub';
    if (strUA.indexOf('googlebot') > -1) arrInfo.name = 'GoogleBot';
    if (strUA.indexOf('msnbot') > -1) arrInfo.name = 'MSN Bot';
    if (strUA.indexOf('slurp') > -1) arrInfo.name = 'Yahoo! Slurp';
    arrInfo.name += strType;
  }
  if (strUA.indexOf('wget') > -1) {
    arrInfo.name = 'Wget';
  }
  if (strUA.indexOf('ask jeeves') > -1 || strUA.indexOf('teoma') > -1) {
    arrInfo.name = 'Ask Jeeves/Teoma';
  }
  if (strUA.indexOf('msie') > -1) {
    strType = '[IE';
    idx = strUA.indexOf('msie');
    idxCol = strUA.indexOf(';', idx);
    strType += ' ' + strUA.substr(idx + 5, idxCol - idx - 5) + ']';
    arrInfo.name = 'IE';
    if (strUA.indexOf('msn') > -1) arrInfo.name = 'MSN';
    if (strUA.indexOf('aol') > -1) arrInfo.name = 'AOL';
    if (strUA.indexOf('webtv') > -1) arrInfo.name = 'WebTV';
    if (strUA.indexOf('myie2') > -1) arrInfo.name = 'MyIE2';
    if (strUA.indexOf('maxthon') > -1) arrInfo.name = 'Maxthon';
    if (strUA.indexOf('gosurf') > -1) arrInfo.name = 'GoSurf';
    if (strUA.indexOf('netcaptor') > -1) arrInfo.name = 'NetCaptor';
    if (strUA.indexOf('sleipnir') > -1) arrInfo.name = 'Sleipnir';
    if (strUA.indexOf('avant browser') > -1) arrInfo.name = 'AvantBrowser';
    if (strUA.indexOf('greenbrowser') > -1) arrInfo.name = 'GreenBrowser';
    if (strUA.indexOf('slimbrowser') > -1) arrInfo.name = 'SlimBrowser';
    arrInfo.name += strType;
  }
  if (strUA.indexOf('opera') > -1) {
    idx = strUA.indexOf('opera');
    arrInfo.name = 'Opera ' + strUA.substr(idx + 6, idx + 9);
  }
  if (strUA.indexOf('applewebkit') > -1) {
    strType = '[AppleWebKit]';
    arrInfo.name = '';
    if (strUA.indexOf('omniweb') > -1) arrInfo.name = 'OmniWeb';
    if (strUA.indexOf('safari') > -1) arrInfo.name = 'Safari';
    arrInfo.name += strType;
  }

  // OS Check
  if (strUA.indexOf('windows') > -1) arrInfo.os = 'Windows';
  if (strUA.indexOf('windows ce') > -1) arrInfo.os = 'Windows CE';
  if (strUA.indexOf('windows 95') > -1) arrInfo.os = 'Windows 95';
  if (strUA.indexOf('win98') > -1) arrInfo.os = 'Windows 98';
  if (strUA.indexOf('windows 98') > -1) arrInfo.os = 'Windows 98';
  if (strUA.indexOf('windows 2000') > -1) arrInfo.os = 'Windows 2000';
  if (strUA.indexOf('windows xp') > -1) arrInfo.os = 'Windows XP';

  if (strUA.indexOf('windows nt') > -1) {
    arrInfo.os = 'Windows NT';
    if (strUA.indexOf('windows nt 5.0') > -1) arrInfo.os = 'Windows 2000';
    if (strUA.indexOf('windows nt 5.1') > -1) arrInfo.os = 'Windows XP';
    if (strUA.indexOf('windows nt 5.2') > -1) arrInfo.os = 'Windows 2003';
  }
  if (strUA.indexOf('x11') > -1 || strUA.indexOf('unix') > -1) arrInfo.os = 'Unix';
  if (strUA.indexOf('sunos') > -1 || strUA.indexOf('sun os') > -1) arrInfo.os = 'SUN OS';
  if (strUA.indexOf('powerpc') > -1 || strUA.indexOf('ppc') > -1) arrInfo.os = 'PowerPC';
  if (strUA.indexOf('macintosh') > -1) arrInfo.os = 'Mac';
  if (strUA.indexOf('mac osx') > -1) arrInfo.os = 'MacOSX';
  if (strUA.indexOf('freebsd') > -1) arrInfo.os = 'FreeBSD';
  if (strUA.indexOf('linux') > -1) arrInfo.os = 'Linux';
  if (strUA.indexOf('palmsource') > -1 || strUA.indexOf('palmos') > -1) arrInfo.os = 'PalmOS';
  if (strUA.indexOf('wap ') > -1) arrInfo.os = 'WAP';

  return arrInfo;
}
